﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Appology.SugarSync.IO.Primitives;
using System.Threading.Tasks;
using System.Net;

namespace Appology.SugarSync.IO
{
    public class SugarSyncFileInfo : SugarSyncPathInfo
    {
        private string _contentType;
        private Uri _dataUri;
        private DateTimeOffset _dateCreated;
        private DateTimeOffset _dateModified;
        private long _length;
        private Uri _publicUri;
        private IEnumerable<SugarSyncFileInfo> _revisions;

        public SugarSyncFileInfo(SugarSyncAccount account, Uri refUri)
            : base(account, refUri)
        {
            DataUri = new Uri(refUri + "/data", UriKind.Absolute);
        }

        public SugarSyncFileInfo(string name, SugarSyncAccount account, Uri refUri)
            : base(account, refUri)
        {
            Name = name;
            DataUri = new Uri(refUri + "/data", UriKind.Absolute);
        }

        public IEnumerable<SugarSyncFileInfo> Revisions
        {
            get { return _revisions; }
            private set
            {
                if (_revisions != value)
                {
                    _revisions = value;
                    OnPropertyChanged("Revisions");
                }
            }
        }

        public string ContentType
        {
            get { return _contentType; }
            private set
            {
                if (_contentType != value)
                {
                    _contentType = value;
                    OnPropertyChanged("ContentType");
                }
            }
        }

        public long Length
        {
            get { return _length; }
            private set
            {
                if (_length != value)
                {
                    _length = value;
                    OnPropertyChanged("Length");
                }
            }
        }

        public DateTimeOffset DateModified
        {
            get { return _dateModified; }
            private set
            {
                if (_dateModified != value)
                {
                    _dateModified = value;
                    OnPropertyChanged("DateModified");
                }
            }
        }

        public DateTimeOffset DateCreated
        {
            get { return _dateCreated; }
            private set
            {
                if (_dateCreated != value)
                {
                    _dateCreated = value;
                    OnPropertyChanged("DateCreated");
                }
            }
        }

        public Uri DataUri
        {
            get { return _dataUri; }
            private set
            {
                if (_dataUri != value)
                {
                    _dataUri = value;
                    OnPropertyChanged("DataUri");
                }
            }
        }

        public bool IsPublicUriEnabled
        {
            get { return PublicUri != null; }
        }

        public Uri PublicUri
        {
            get { return _publicUri; }
            set
            {
                if (_publicUri != value)
                {
                    _publicUri = value;
                    OnPropertyChanged("PublicUri");
                    OnPropertyChanged("IsPublicUriEnabled");
                }
            }
        }

        private bool _isBusy;

        public override bool IsBusy
        {
            get { return _isBusy; }
            set
            {
                if (_isBusy != value)
                {
                    _isBusy = value;
                    OnPropertyChanged("IsBusy");
                }
            }
        }

        public async Task<Stream> OpenReadAsync()
        {
            IsBusy = true;

            var webClient = await Account.GetWebClientAsync();

            var stream = await webClient.OpenReadTaskAsync(DataUri);

            IsBusy = false;

            return stream;
        }

        public async Task<Stream> OpenWriteAsync()
        {
            IsBusy = true;

            var webClient = await Account.GetWebClientAsync();

            var stream = await webClient.OpenWriteTaskAsync(DataUri, "PUT");

            IsBusy = false;

            return stream;
        }

        public async Task ReadStringAsync()
        {
            IsBusy = true;

            using (var sr = new StreamReader(await OpenReadAsync()))
            {
                string result = sr.ReadToEnd();
                sr.Close();
            }

            IsBusy = false;
        }

        public async Task WriteStringAsync(string content)
        {
            IsBusy = true;

            using (var sw = new StreamWriter(await OpenWriteAsync()))
            {
                sw.Write(content);
                sw.Flush();
                sw.Close();
            }

            IsBusy = false;
        }

        public async Task<Stream> OpenPreviewImageAsync(int maxWidth, int maxHeight, bool square, int numClockwiseRotations)
        {
            Stream stream = null;

            IsBusy = true;

            var webClient = await Account.GetWebClientAsync();

            webClient.Headers["Accept"] = string.Format("image/jpeg; pxmax={0};pymax={1};sq=({2});r=({3});", maxWidth, maxHeight, square ? 1 : 0, numClockwiseRotations);

            stream = await webClient.OpenReadTaskAsync(DataUri);

            IsBusy = false;

            return stream;
        }

        public async Task MakePublic()
        {
            /*
            <?xml version="1.0" encoding="UTF-8"?>
            <file>
                <publicLink enabled="true"/>
            </file>
            */

            if (IsPublicUriEnabled)
            {
                return;
            }

            IsBusy = true;

            var webClient = await Account.GetWebClientAsync();

            XElement file = XElement.Parse("<file/>");

            XElement publicLink = XElement.Parse("<publicLink/>");
            publicLink.Add(new XAttribute("enabled", "true"));

            file.Add(publicLink);

            using (Stream stream = await webClient.OpenWriteTaskAsync(RefUri, "PUT"))
            {
                byte[] buffer = Encoding.UTF8.GetBytes(file.ToString());
                stream.Write(buffer, 0, buffer.Length);
                stream.Flush();
                stream.Close();
            }

            IsBusy = false;
        }

        public async Task MakePrivate()
        {
            /*
            <?xml version="1.0" encoding="UTF-8"?>
            <file>
                <publicLink enabled="false"/>
            </file>
            */

            if (!IsPublicUriEnabled)
            {
                return;
            }

            IsBusy = true;

            var webClient = await Account.GetWebClientAsync();

            XElement file = XElement.Parse("<file/>");

            XElement publicLink = XElement.Parse("<publicLink/>");
            publicLink.Add(new XAttribute("enabled", "false"));

            file.Add(publicLink);

            using (Stream stream = await webClient.OpenWriteTaskAsync(RefUri, "PUT"))
            {
                byte[] buffer = Encoding.UTF8.GetBytes(file.ToString());
                stream.Write(buffer, 0, buffer.Length);
                stream.Flush();
                stream.Close();
            }

            IsBusy = false;
        }

        public async Task UpdateInfo(string newName, string newContentType)
        {
            /*
            <file>
                <displayName>newFileName</displayName>
                <mediaType>image/jpeg</mediaType>
            </file>
            */

            IsBusy = true;

            var webClient = await Account.GetWebClientAsync();

            XElement file = XElement.Parse("<file/>");

            XElement displayName = XElement.Parse("<displayName/>");
            XElement mediaType = XElement.Parse("<mediaType/>");

            displayName.Value = newName;
            mediaType.Value = newContentType;

            file.Add(displayName);
            file.Add(mediaType);

            using (var stream = await webClient.OpenWriteTaskAsync(RefUri, "PUT"))
            {
                byte[] buffer = Encoding.UTF8.GetBytes(file.ToString());
                stream.Write(buffer, 0, buffer.Length);
                stream.Flush();
                stream.Close();
            }

            IsBusy = false;
        }

        public async Task Delete()
        {
            IsBusy = true;

            var webClient = await Account.GetWebClientAsync();

            using (var stream = await webClient.OpenWriteTaskAsync(RefUri, "DELETE"))
            {
                stream.Close();
            }

            IsBusy = false;
        }

        public async override Task RefreshAsync()
        {
            IsBusy = true;

            var result = await Account.GetXmlAsync(RefUri);

            /*
                                                <file>
                                                    <displayName>ExpressionStudio_Dspk_Ultimate4_en.iso</displayName>
                                                    <dsid>/sc/877921/1813437_11165</dsid>
                                                    <timeCreated>2011-01-30T18:13:21.000-08:00</timeCreated>
                                                    <parent>https://api.sugarsync.com/folder/:sc:877921:1813437_11159</parent>
                                                    <size>297947136</size>
                                                    <lastModified>2010-07-21T21:07:26.000-07:00</lastModified>
                                                    <mediaType>application/octet-stream</mediaType>
                                                    <presentOnServer>true</presentOnServer>
                                                    <fileData>https://api.sugarsync.com/file/:sc:877921:1813437_11165/data</fileData>
                                                    <versions>https://api.sugarsync.com/file/:sc:877921:1813437_11165/version</versions>
                                                    <publicLink enabled="false"/>
                                                </file>
                                                */

            Name = result.Element("displayName").Value;
            ContentType = result.Element("mediaType").Value;
            Length = Convert.ToInt64(result.Element("size").Value);
            DateModified = DateTimeOffset.ParseExact(result.Element("lastModified").Value, "yyyy-MM-ddTHH:mm:ss.fffzzz", CultureInfo.InvariantCulture);
            DateCreated = DateTimeOffset.ParseExact(result.Element("timeCreated").Value, "yyyy-MM-ddTHH:mm:ss.fffzzz", CultureInfo.InvariantCulture);

            ParentRefUri = new Uri(result.Element("parent").Value, UriKind.Absolute);

            LastUpdated = DateTimeOffset.Now;

            XElement publicLinkElement = result.Element("publicLink");

            if (publicLinkElement != null && publicLinkElement.Attribute("enabled").Value == "true")
            {
                PublicUri = new Uri(publicLinkElement.Value, UriKind.Absolute);
            }
            else
            {
                PublicUri = null;
            }

            XElement fileData = result.Element("fileData");
            if (fileData != null)
            {
                DataUri = new Uri(fileData.Value, UriKind.Absolute);
            }
            
            IsBusy = false;
        }

        public static async Task<SugarSyncFileInfo> FindFile(string path, SugarSyncAccount account)
        {
            var directory = Path.GetDirectoryName(path);
            var filename = Path.GetFileName(path);

            var directoryInfo = await SugarSyncDirectoryInfo.FindDirectory(directory, account);

            await directoryInfo.RefreshAsync();

            var fileInfo = (from f in directoryInfo.Contents.OfType<SugarSyncFileInfo>()
                            where f.Name.Equals(filename, StringComparison.InvariantCultureIgnoreCase)
                            select f).SingleOrDefault();

            if (fileInfo == null)
            {
                throw new Exception(string.Format("Could not find part of path \"{0}\".", path));
            }

            return fileInfo;
        }

        public static async Task<SugarSyncFileInfo> CreateFile(string path, string contentType, bool ensureDirectoriesExist, bool overwrite, SugarSyncAccount account)
        {
            var directory = Path.GetDirectoryName(path);
            var filename = Path.GetFileName(path);

            SugarSyncDirectoryInfo directoryInfo = null;

            if (ensureDirectoriesExist)
            {
                directoryInfo = await SugarSyncDirectoryInfo.CreateDirectory(directory, true, account);
            }
            else
            {
                directoryInfo = await SugarSyncDirectoryInfo.FindDirectory(directory, account);
            }

            await directoryInfo.RefreshAsync();

            var fileInfo = (from f in directoryInfo.Contents.OfType<SugarSyncFileInfo>()
                            where f.Name.Equals(filename, StringComparison.InvariantCultureIgnoreCase)
                            select f).SingleOrDefault();

            if (fileInfo != null)
            {
                if (overwrite)
                {
                    await fileInfo.Delete();
                }
                else
                {
                    throw new Exception(string.Format("File \"{0}\" already exists.", path));
                }
            }

            fileInfo = await directoryInfo.CreateFileInfo(filename, contentType);

            return fileInfo;
        }
    }
}